package core

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"errors"
	"fmt"
	"gitee.com/KotlinToGo/xorm/ictx"
	"gitee.com/KotlinToGo/xorm/log"
	"gitee.com/KotlinToGo/xorm/mapper"
	"reflect"
	"regexp"
	"sync"
)

var (
	// DefaultCacheSize 设置默认缓存大小
	DefaultCacheSize = 200
	// ErrNoMapPointer 没有map指针时表示错误
	ErrNoMapPointer = errors.New("mp should be a map's pointer")
	// ErrNoStructPointer 没有结构体指针时表示错误
	ErrNoStructPointer      = errors.New("mp should be a structure's pointer")
	_ ISQL = &DB{}
)

type cacheStruct struct {
	value reflect.Value
	idx   int
}

// DB 是 *sql.DB 的扩展。
type DB struct {
	*sql.DB
	Mapper mapper.IMapper
	cache  map[reflect.Type]*cacheStruct
	mu     sync.RWMutex
	Logger log.IContextLogger
	hooks  ictx.Hooks
}

// Open 打开一个数据库，并且 Ping()一下。返回的是一个已经连接的 *DB 对象。
func Open(driverName, dataSourceName string) (*DB, error) {
	db, err := sql.Open(driverName, dataSourceName)
	if err != nil {
		return nil, err
	}
	err = db.Ping()
	if err != nil {
		return nil, err
	}
	return &DB{
		DB:     db,
		Mapper: mapper.NewCacheMapper(&mapper.SnakeMapper{}),
		cache:  make(map[reflect.Type]*cacheStruct),
	}, nil
}

// FromDB 从sql.DB创建数据库。
func FromDB(db *sql.DB) *DB {
	return &DB{
		DB:     db,
		Mapper: mapper.NewCacheMapper(&mapper.SnakeMapper{}),
		cache:  make(map[reflect.Type]*cacheStruct),
	}
}

// NeedLogSQL 如果需要记录SQL，则返回true
func (db *DB) NeedLogSQL(ctx context.Context) bool {
	if db.Logger == nil {
		return false
	}

	v := ctx.Value(log.SessionShowSQLKey)
	if showSQL, ok := v.(bool); ok {
		return showSQL
	}
	return db.Logger.IsShowSQL()
}

func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
	db.mu.Lock()
	defer db.mu.Unlock()
	cs, ok := db.cache[typ]
	if !ok || cs.idx+1 > DefaultCacheSize-1 {
		cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
		db.cache[typ] = cs
	} else {
		cs.idx = cs.idx + 1
	}
	return cs.value.Index(cs.idx).Addr()
}

func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
	vv := reflect.ValueOf(mp)
	if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
		return "", []interface{}{}, ErrNoMapPointer
	}

	args := make([]interface{}, 0, len(vv.Elem().MapKeys()))
	var err error
	query = re.ReplaceAllStringFunc(query, func(src string) string {
		v := vv.Elem().MapIndex(reflect.ValueOf(src[1:]))
		if !v.IsValid() {
			err = fmt.Errorf("map key %s is missing", src[1:])
		} else {
			args = append(args, v.Interface())
		}
		return "?"
	})

	return query, args, err
}

func StructToSlice(query string, st interface{}) (string, []interface{}, error) {
	vv := reflect.ValueOf(st)
	if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
		return "", []interface{}{}, ErrNoStructPointer
	}

	args := make([]interface{}, 0)
	var err error
	query = re.ReplaceAllStringFunc(query, func(src string) string {
		fv := vv.Elem().FieldByName(src[1:]).Interface()
		if v, ok := fv.(driver.Valuer); ok {
			var value driver.Value
			value, err = v.Value()
			if err != nil {
				return "?"
			}
			args = append(args, value)
		} else {
			args = append(args, fv)
		}
		return "?"
	})
	if err != nil {
		return "", []interface{}{}, err
	}
	return query, args, nil
}

// QueryContext 覆盖 sql.DB.QueryContext。
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
	hookCtx := ictx.NewContextHook(ctx, query, args)
	ctx, err := db.beforeProcess(hookCtx)
	if err != nil {
		return nil, err
	}
	rows, err := db.DB.QueryContext(ctx, query, args...)
	hookCtx.End(ctx, nil, err)
	if err := db.afterProcess(hookCtx); err != nil {
		if rows != nil {
			rows.Close()
		}
		return nil, err
	}
	return &Rows{rows, db}, nil
}

// Query 覆盖 sql.DB.Query。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
	return db.QueryContext(context.Background(), query, args...)
}

// QueryMapContext 通过map和context执行带有参数的查询。
func (db *DB) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) {
	query, args, err := MapToSlice(query, mp)
	if err != nil {
		return nil, err
	}
	return db.QueryContext(ctx, query, args...)
}

// QueryMap 通过map执行带有参数的查询
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
	return db.QueryMapContext(context.Background(), query, mp)
}

func (db *DB) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) {
	query, args, err := StructToSlice(query, st)
	if err != nil {
		return nil, err
	}
	return db.QueryContext(ctx, query, args...)
}

func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) {
	return db.QueryStructContext(context.Background(), query, st)
}

func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row {
	rows, err := db.QueryContext(ctx, query, args...)
	if err != nil {
		return &Row{nil, err}
	}
	return &Row{rows, nil}
}

func (db *DB) QueryRow(query string, args ...interface{}) *Row {
	return db.QueryRowContext(context.Background(), query, args...)
}

func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row {
	query, args, err := MapToSlice(query, mp)
	if err != nil {
		return &Row{nil, err}
	}
	return db.QueryRowContext(ctx, query, args...)
}

func (db *DB) QueryRowMap(query string, mp interface{}) *Row {
	return db.QueryRowMapContext(context.Background(), query, mp)
}

func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row {
	query, args, err := StructToSlice(query, st)
	if err != nil {
		return &Row{nil, err}
	}
	return db.QueryRowContext(ctx, query, args...)
}

func (db *DB) QueryRowStruct(query string, st interface{}) *Row {
	return db.QueryRowStructContext(context.Background(), query, st)
}

var (
	re = regexp.MustCompile(`[?](\w+)`)
)

// ExecMapContext exec map with ictx.ContextHook
// insert into (name) values (?)
// insert into (name) values (?name)
func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) {
	query, args, err := MapToSlice(query, mp)
	if err != nil {
		return nil, err
	}
	return db.ExecContext(ctx, query, args...)
}

func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) {
	return db.ExecMapContext(context.Background(), query, mp)
}

func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) {
	query, args, err := StructToSlice(query, st)
	if err != nil {
		return nil, err
	}
	return db.ExecContext(ctx, query, args...)
}

func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	hookCtx := ictx.NewContextHook(ctx, query, args)
	ctx, err := db.beforeProcess(hookCtx)
	if err != nil {
		return nil, err
	}
	res, err := db.DB.ExecContext(ctx, query, args...)
	hookCtx.End(ctx, res, err)
	if err := db.afterProcess(hookCtx); err != nil {
		return nil, err
	}
	return res, nil
}

func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) {
	return db.ExecStructContext(context.Background(), query, st)
}

func (db *DB) beforeProcess(c *ictx.CtxHook) (context.Context, error) {
	if db.NeedLogSQL(c.Ctx) {
		db.Logger.BeforeSQL(log.LoggerCtx(*c))
	}
	ctx, err := db.hooks.BeforeProcess(c)
	if err != nil {
		return nil, err
	}
	return ctx, nil
}

func (db *DB) afterProcess(c *ictx.CtxHook) error {
	err := db.hooks.AfterProcess(c)
	if db.NeedLogSQL(c.Ctx) {
		db.Logger.AfterSQL(log.LoggerCtx(*c))
	}
	return err
}

func (db *DB) AddHook(h ...ictx.IHook) {
	db.hooks.AddHook(h...)
}
